#include "Octree.hpp"

#include "OpenGL.hpp"
#include "AxisAlignedBox.hpp"
#include "Vectors.hpp"
#include "Debug.hpp"
#include "Visible.hpp"
#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

using namespace cge;

Octree::Octree( const AxisAlignedBox& bounds, unsigned maxDepth )
: bounds_(bounds) {
	clearOctants_();
	parent_ = 0;
	maxDepth_ = maxDepth;
	updateBounds_();
}

Octree::Octree( Octree* parent ) {
	CGE_ASSERT( parent_ );
	CGE_ASSERT( parent->maxDepth_ > 0 );
	
	clearOctants_();
	parent_ = parent;
	maxDepth_ = parent->maxDepth_-1;
}

Octree::Octree( Octree* parent, const AxisAlignedBox &box )
: bounds_(box) {
	clearOctants_();
	parent_ = parent;
	updateBounds_();
}

Octree::~Octree() {
	foreach(Visible* v, vis_)
		v->octree_ = 0;

	foreach(Octree* o, octants_)
		delete o;
}

void Octree::clearOctants_() {
	auto end = octants_ + 8;
	for(auto i = octants_; i != end; ++i)
		*i = 0;
}

void Octree::addVisibleHere_( Visible *vis ) {
	CGE_ASSERT( vis->octree_ != this );
	CGE_ASSERT( vis->octree_ == 0 );
	vis->octree_ = this;
	vis_.push_back(vis);
	vis->iter_ = vis_.rbegin().base();
}

void Octree::removeVisible_ ( Visible *vis ) {
	CGE_ASSERT( vis->octree_ == this );
	vis_.erase(vis->iter_);
	vis->octree_ = 0;
}

Octree*& Octree::getOctant(int x, int y, int z) {
	return octants_[z*4+y*2+x];
}

void Octree::addVisible( Visible* visible) {
	Octree* node = this;
	vec3    visSize   = visible->getBoundingBox().getSize();
	vec3    visCenter = visible->getBoundingBox().getCenter();
	int     isRight, isAbove, isNear;

	while( node->maxDepth_ > 0 && node->shouldPropagate( visSize ) ) {
		isRight  = visCenter.x > node->center_.x;
		isAbove  = visCenter.y > node->center_.y;
		isNear   = visCenter.z > node->center_.z;
		
		Octree*& octant = node->getOctant(isRight,isAbove,isNear);
		
		if( !octant ) {
			octant = new Octree( node );
			
			vec3&       cmin = octant->bounds_.getMinimum();
			vec3&       cmax = octant->bounds_.getMaximum();
			const vec3& nmin = node->bounds_.getMinimum();
			const vec3& nmax = node->bounds_.getMaximum();
			const vec3& ncen = node->center_;

			if( isRight ) { cmin.x = ncen.x; cmax.x = nmax.x; }
			else          { cmin.x = nmin.x; cmax.x = ncen.x; }
			
			if( isAbove ) { cmin.y = ncen.y; cmax.y = nmax.y; }
			else          { cmin.y = nmin.y; cmax.y = ncen.y; }
			
			if( isNear  ) { cmin.z = ncen.z; cmax.z = nmax.z; }
			else          { cmin.z = nmin.z; cmax.z = ncen.z; }

			octant->updateBounds_();
		}

		node = octant;
	}

	node->addVisibleHere_( visible );
}
